8.3 同步

通道并非用来取代锁的,它们有各自不同的使用场景。通道倾向于解决逻辑层次的并发处理架构,而锁则用来保护局部范围内的数据安全。

标准库sync提供了互斥和读写锁,另有原子操作等,可基本满足日常开发需要。Mutex、RWMutex的使用并不复杂,只有几个地方需要注意。

将Mutex作为匿名字段时,相关方法必须实现为pointer-receiver,否则会因复制导致锁机制失效。

type data struct{ sync.Mutex }

func(d data)test(s string) { d.Lock() defer d.Unlock()

for i:=0;i<5;i++ { println(s,i) time.Sleep(time.Second) } }

func main() { var wg sync.WaitGroup wg.Add(2)

var d data

go func() { defer wg.Done() d.test(“read”) }()

go func() { defer wg.Done() d.test(“write”) }()

wg.Wait() }

输出:

write 0 read 0 read 1 write 1 write 2 read 2 read 3 write 3 write 4 read 4

锁失效,将receiver类型改为*data后正常。

也可用嵌入*Mutex来避免复制问题,但那需要专门初始化。

应将Mutex锁粒度控制在最小范围内,及早释放。

// 错误用法 func doSomething() { m.Lock() url:=cache[“key”] http.Get(url) // 该操作并不需要锁保护 m.Unlock()
}

// 正确用法 func doSomething() { m.Lock() url:=cache[“key”] m.Unlock() // 如使用defer,则依旧将Get保护在内 http.Get(url) }

Mutex不支持递归锁,即便在同一goroutine下也会导致死锁。

func main() { var m sync.Mutex

m.Lock() { m.Lock() m.Unlock() } m.Unlock() }

输出:

fatal error:all goroutines are asleep-deadlock!

在设计并发安全类型时,千万注意此类问题。

type cache struct{ sync.Mutex data[]int }

func(c*cache)count()int{ c.Lock() n:=len(c.data) c.Unlock()

return n }

func(c*cache)get()int{ c.Lock() defer c.Unlock()

var d int if n:=c.count();n>0{ //count重复锁定,导致死锁 d=c.data[0] c.data=c.data[1:] }

return d }

func main() { c:=cache{ data: []int{1,2,3,4}, }

println(c.get()) }

输出:

fatal error:all goroutines are asleep-deadlock!

相关建议:

  • 对性能要求较高时,应避免使用defer Unlock。
  • 读写并发时,用RWMutex性能会更好一些。
  • 对单个数据读写保护,可尝试用原子操作。
  • 执行严格测试,尽可能打开数据竞争检查。